package com.trg.flex
{
	import flash.net.getClassByAlias;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.binding.utils.BindingUtils;
	import mx.controls.Alert;
	import mx.utils.DescribeTypeCache;
	import mx.utils.ObjectUtil;
	
	/**
	 * This class houses a lot of handy functions for dealing with classes, types and metadata.
	 */
	public class MetaDataUtils
	{
		/**
		 * Return the Class object associated with an object.
		 */
		public static function getType(object:Object):Class {
			if (object == null) return null;
			return object.constructor;
		}
		
		/**
		 * Return the Class object associated with the given property
		 * of the object or Class. The property may be nested. The
		 * results will be the same for a Class or an instance of a Class.
		 */
		public static function getPropertyType(object:Object, property:String):Class {
			var chain:Array = FlexUtils.getChain(property);
			var klass:Class;
			if (object is Class) {
				klass = object as Class;
			} else {
				klass = object.constructor;
			}
			
			for (var i:int = 0; i < chain.length; i++) {
				var xml:XML = describeType(klass);
				var list:XMLList = xml.factory.accessor.(@name==chain[i]).@type;
				if (list.length() < 1) {
					list = xml.factory.variable.(@name==chain[i]).@type;
				}
				if (list.length() < 1) return null;
				if (list[0] == "*") {
					klass = Object;
				} else {
					klass = getDefinitionByName(String(list[0])) as Class;
				}
			}
			
			return klass;
		}
		
		/**
		 * Return the METADATA object of the given object or the given
		 * property of the given object if property is specified. The
		 * property may be nested.  The results will be the same for a
		 * Class or an instance of a Class.
		 */
		public static function getClassMetaData(object:Object, property:String=null):Object {
			var klass:Class;
			if (property == null || property.length == 0) {
				if (object is Class)
					klass = object as Class;
				else
					klass = object.constructor;
			} else {
				klass = getPropertyType(object, property);
			}
			return klass.METADATA;
		}
		
		/**
		 * Get the part of the METADATA specific to the given property for the
		 * given object. The property may be nested.  The results will be the
		 * same for a Class or an instance of a Class.
		 */
		public static function getPropertyMetaData(object:Object, property:String):Object {
			var pos:int = property.indexOf(".");
			var classMetaData:Object
			if (pos == -1) {
				classMetaData = getClassMetaData(object);
			} else {
				classMetaData = getClassMetaData(object, property.substring(0, pos));
				property = property.substring(pos + 1);
			}
			if (classMetaData == null || classMetaData.properties == null) return null;
			return classMetaData.properties[property];
		}
		
		/**
		 * Return the fully qualified name of the associated server-side class
		 * of the given object or Class.
		 */
		public static function getRemoteClass(object:*):String {
			if (object == null) return null;
			return describeType(object).@alias;
		}
		
		/**
		 * Return the VO Class that is associated with the given fully qualified
		 * server-side class name.
		 */
		public static function getTypeFromRemoteClass(remoteClass:String):Class {
			return getClassByAlias(remoteClass);
		}
		
		/**
		 * If superClass is an interface, return true if klass is superClass or
		 * implements superClass; otherwise return true if klass is superClass
		 * or a subclass of superClass.
		 */
		public static function typeIs(klass:Class, superClass:Class):Boolean {
			if (klass == superClass) return true;
			var superName:String = getQualifiedClassName(superClass);
			var xml:XMLList = describeType(klass).factory;
			if (xml.extendsClass.(@type == superName).length() == 1) return true;
			if (xml.implementsInterface.(@type == superName).length() == 1) return true;
			return false;
		}
		
		/**
		 * Return a map where the keys are all the properties of the given object or
		 * Class that are VO types. The values are the associated Class objects for
		 * those property types.
		 */
		public static function getForeignKeys(klass:Object):Object {
			if (!(klass is Class))
				klass = klass.constructor;
			var propList:Object = {};
			var xml:XMLList = describeType(klass).factory;
			for each (var accessor:* in xml.accessor.(@access == "readwrite")) {
				if (accessor.@type != "*" && typeIs(getDefinitionByName(accessor.@type) as Class, IValueObject)) {
					propList[accessor.@name.toString()] = accessor.@type.toString();
				}
			}
			for each (var variable:* in xml.variable) {
				if (variable.@type != "*" && typeIs(getDefinitionByName(variable.@type) as Class, IValueObject)) {
					propList[variable.@name.toString()] = variable.@type.toString();
				}
			}
			return propList;
		}
		
		public static function getName(o:*):String {
			if (o == null) return null;
			if (!(o is Class)) {
				o = o.constructor;
			}
			return describeType(o).@name;
		}
		
		public static function getSimpleName(o:*):String {
			if (o == null) return null;
			var name:String = getName(o);
			var pos:int = name.lastIndexOf(':');
			return name.substr(pos + 1);
		}
		
		public static function describeType(o:*):XML {
			//return DescribeTypeCache.describeType(o).typeDescription;
			return flash.utils.describeType(o);
		}
	}
}